package Algorithms;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Vector;

import runner.DateBD;

import Route.Legatura;
import Route.Statie;

/**
 * 
 * @author Cristina
 *
 */

public class UniformCost {
	PrintWriter logWriter = null;
	File log;
	Vector<Statie> closedSet = new Vector<Statie>();
	Vector<Statie> openSet = new Vector<Statie>();
	Statie [] cameFrom = new Statie[300];
	int [] gScore = new int[DateBD.cs.total()];
	Vector<Statie> fii = new Vector<Statie>();
	Statie start;
	Statie end;
	
	String p;
	
	/**
	 * 
	 * @param start Statia de unde se pleaca
	 * @param end Statia destinatie
	 * @param log Fisierul unde se retine solutia
	 */
	public UniformCost(Statie start,Statie end,String log){
		this.start = start;
		this.end = end;
		try {
			logWriter = new PrintWriter(new FileWriter(log));
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.log = new File(log);
	}
	
	/**
	 * 
	 * @return Returneaza statia de inceput
	 */
	public Statie getStart(){
		return start;
	}
	
	/**
	 * 
	 * @return Returneaza statia destinatie
	 */
	public Statie getEnd(){
		return end;
	}
	
	/**
	 * 
	 * @return Statia din Open Set cu costul drumului minim
	 */
	public Statie costMin(){
		int k = 0;
		int min = gScore[openSet.get(0).getCod()];
		for(int i = 1;i<openSet.size();i++){
			if(min>gScore[openSet.get(i).getCod()]){
				min = gScore[openSet.get(i).getCod()];
				k = i;
			}
		}
		return openSet.get(k);
	}
	
	/**
	 * 
	 * @param parinte Nodul pentru care se genereaza toti fiii
	 */
	public void getFii(Statie parinte){
		for(int i = 0;i<DateBD.cl.total();i++){
			if(((Legatura)(DateBD.cl.getContinut().get(i))).getStart() == parinte)
				fii.add(((Legatura)(DateBD.cl.getContinut().get(i))).getEnd());
		}
	}
	
	/**
	 * 
	 * @param s Statia ce se cauta in vector
	 * @param set Vectorul unde se efectueaza cautarea
	 * @return Valoare booleana; true, daca statia se gaseste in vector, false in caz contrar
	 */
	public boolean searchInSet(Statie s,Vector<Statie> set){
		for(int i = 0;i<set.size();i++)
			if(set.get(i) == s)
				return true;
		return false;
	}
	
	/**
	 * 
	 * @param x Statia unde sunt
	 * @param y Statia unde vreau sa ajung
	 * @return Distanta dintre cele doua statii, x si y
	 */
	public int distBetween(Statie x,Statie y){
		for(int i = 0;i<DateBD.cl.total();i++){
			if(((Legatura)(DateBD.cl.getContinut().get(i))).getStart() == x)
				if(((Legatura)(DateBD.cl.getContinut().get(i))).getEnd() == y)
					return ((Legatura)(DateBD.cl.getContinut().get(i))).getDurata();
		}
		return -1;
	}
	
	/**
	 * 
	 * @param start Nodul de start
	 * @param goal Nodul destinatie
	 * @return Returneaza un string ce reprezinta solutia problemei sau "Failure"
	 */
	public String uniformCost(Statie start,Statie goal){
		Statie x;
		Statie y;
		int tentativeGScore;
		boolean tentativeIsBetter;
		openSet.add(start);
		gScore[start.getCod()] = 0;
		while(openSet.size() != 0){
			x = costMin();
			if(x == goal)
				return reconstructPath(cameFrom, cameFrom[goal.getCod()])+" \n" +goal.getNume();
			openSet.remove(x);
			closedSet.add(x);
			getFii(x);
			for(int i = 0;i<fii.size();i++){
				y = fii.get(i);
				if(searchInSet(y,closedSet))
				  {
					i++;
					if(i<fii.size())
						y = fii.get(i);
					else
						break;
				  }
				tentativeGScore = gScore[x.getCod()] + distBetween(x,y);

				if(!searchInSet(y,openSet)) {
					openSet.add(y);
					tentativeIsBetter = true;
				}
				else
					if(tentativeGScore < gScore[y.getCod()])
						tentativeIsBetter = true;
					else
						tentativeIsBetter = false;
				if(tentativeIsBetter == true){
					cameFrom[y.getCod()] = x;
					gScore[y.getCod()] = tentativeGScore;
				}
			}
		}
        return "Failure";
    }
		
	/**
	 * 
	 * @param cameFrom Vectorul de statii in care retinem parintele fiecarui nod din solutie
	 * @param currentNode Nodul curent pentru care calculam cine e parintele
	 * @return Returneaza un string care reprezinta solutia
	 */
	public String reconstructPath(Statie [] cameFrom,Statie currentNode){
		if(currentNode != start){
			p = reconstructPath(cameFrom, cameFrom[currentNode.getCod()]);
			return (p+ " \n" + currentNode.getNume());
		}
		else
			return currentNode.getNume();
	}
	
	/**
	 * Executarea algoritmului
	 */
	public void executeAlgorithm(){
		print();
		closeLog();
	}
	
	/**
	 * Afisarea solutiei in fisier
	 */
	private void print()
	{
		logWriter.append("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		logWriter.append(uniformCost(start,end));
		logWriter.append('\n');
	}
	
	/**
	 * Inchiderea fisierului folosit pentru serializare
	 */
	private void closeLog()
	{
		System.out.println("Solution found using Uniform Cost Search... Stacktrace can be found in: " + log.getAbsolutePath());
		logWriter.close();
	}

}
